package cn.myj2c;

import org.apache.commons.io.IOUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

import java.io.IOException;
import java.util.jar.JarFile;

import static org.objectweb.asm.Opcodes.*;

public class FindAllatoriMethod {
    public static void main(String[] args) {
        JarFile inputJar = null;
        try {
            inputJar = new JarFile("D:\\work\\allatori-deobfuscator\\obfuscator\\allatoriDeobfuscator.jar");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        JarFile finalInputJar = inputJar;
        inputJar.stream().forEach(entry -> {
            if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
                return;
            }
            byte[] src = null;
            try {
                src = IOUtils.toByteArray(finalInputJar.getInputStream(entry));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            ClassReader classReader = new ClassReader(src);
            ClassNode classNode = new ClassNode();
            classReader.accept(classNode, ClassReader.SKIP_DEBUG);

            for (MethodNode method : classNode.methods) {
                if(isAllatoriMethod(method)){
                    System.out.println(method.name);
                }
            }
        });
    }

    private static boolean isAllatoriMethod(MethodNode decryptorNode) {
        boolean isAllatori = true;
        isAllatori = isAllatori && containsInvokeVirtual(decryptorNode, "java/lang/String", "charAt", "(I)C");
        isAllatori = isAllatori && containsInvokeVirtual(decryptorNode, "java/lang/String", "length", "()I");
        isAllatori = isAllatori && containsInvokeSpecial(decryptorNode, "java/lang/String", "<init>", null);
        isAllatori = isAllatori && countOccurencesOf(decryptorNode, IXOR) > 2;
        isAllatori = isAllatori && countOccurencesOf(decryptorNode, NEWARRAY) > 0;
        return isAllatori;
    }

    public static boolean containsInvokeVirtual(MethodNode methodNode, String owner, String name, String desc) {
        for (AbstractInsnNode insn : methodNode.instructions) {
            if (isInvokeVirtual(insn, owner, name, desc)) {
                return true;
            }
        }
        return false;
    }

    public static boolean isInvokeVirtual(AbstractInsnNode insn, String owner, String name, String desc) {
        if (insn == null) {
            return false;
        }
        if (insn.getOpcode() != INVOKEVIRTUAL) {
            return false;
        }
        MethodInsnNode methodInsnNode = (MethodInsnNode) insn;
        return (owner == null || methodInsnNode.owner.equals(owner)) &&
                (name == null || methodInsnNode.name.equals(name)) &&
                (desc == null || methodInsnNode.desc.equals(desc));
    }

    public static boolean containsInvokeSpecial(MethodNode methodNode, String owner, String name, String desc) {
        for (AbstractInsnNode insn : methodNode.instructions) {
            if (isInvokeSpecial(insn, owner, name, desc)) {
                return true;
            }
        }
        return false;
    }

    public static boolean isInvokeSpecial(AbstractInsnNode insn, String owner, String name, String desc) {
        if (insn == null) {
            return false;
        }
        if (insn.getOpcode() != INVOKESPECIAL) {
            return false;
        }
        MethodInsnNode methodInsnNode = (MethodInsnNode) insn;
        return (owner == null || methodInsnNode.owner.equals(owner)) &&
                (name == null || methodInsnNode.name.equals(name)) &&
                (desc == null || methodInsnNode.desc.equals(desc));
    }

    public static int countOccurencesOf(MethodNode methodNode, int opcode) {
        int i = 0;
        for (AbstractInsnNode insnNode : methodNode.instructions) {
            if (insnNode.getOpcode() == opcode) {
                i++;
            }
        }
        return i;
    }
}
